/*
=======================================================================

  Graphic effects used in BOTS

=======================================================================
*/

#include "cg_local.h"
#include "cgame_BOTS.h"
#include "..\game\inv.h"

/////////////////////////////////////////////
// CG_BOTS_Laser
// 
// applies a railCoreShader to an entity - sharks with fricking laser beams!!!
//
void CG_BOTS_Laser( centity_t* cent, int entType )
{
	refEntity_t		beam;
	clientInfo_t	*ci;
	float*			color;

	ci = &cgs.clientinfo[ cent->currentState.clientNum ];

	memset( &beam, 0, sizeof( beam ) );

	// set the start and end of beam from previously set start/end points
	VectorCopy(cent->currentState.origin, beam.origin);
	VectorCopy(cent->currentState.origin2, beam.oldorigin);

	if (entType == ET_BOTS_LASER)
	{
		beam.reType = RT_RAIL_CORE;
		beam.customShader = cgs.media.railCoreShader;
	}
	else
	{
		beam.reType = RT_RAIL_RINGS;
		beam.customShader = cgs.media.railRingsShader;
	}

	AxisClear( beam.axis );

	color = CG_TeamColor(ci->team);
	beam.shaderRGBA[0] = color[0] * 255;
	beam.shaderRGBA[1] = color[1] * 255;
	beam.shaderRGBA[2] = color[2] * 255;
	beam.shaderRGBA[3] = color[3] * 255;

	trap_R_AddRefEntityToScene( &beam );
}

void CG_BOTS_flare(centity_t* cent)
{
	int i;

	for (i = 0; i < 2; i++)
		trap_R_AddLightToScene( cent->currentState.origin, 300, 1, 1, 1 );
}

// This is to coordinate with the "game.c" code to keep the prediction correct 
// based on the speed changes
float CG_BOTS_classSpeed()
{
	const char* info;		// info string containing client data
	int			clientNum;	// current client number
	int			li_class;	// client's current class (captain, bodyguard, ...)
	int			li_level;	// client's current promotion level
	int			li_speed;

	clientNum = cg.snap->ps.clientNum;

	info     = CG_ConfigString(CS_BOTS_CLIENT + clientNum);
	li_class = atoi(Info_ValueForKey(info, "c"));
	li_level = atoi(Info_ValueForKey(info, "lev"));

	if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED) {
		li_speed = 0.9;
	}
	else {
		li_speed = 1.1;
	}

	switch (li_class) 
	{
		case BOTS_CAPTAIN:
			li_speed *= 0.9;
			break;
		case BOTS_BODYGUARD:
			li_speed *= 1.0;
			break;
		case BOTS_SNIPER:
			li_speed *= 1.1;
			break;
		case BOTS_SOLDIER:
			li_speed *= 0.9;
			break;
		case BOTS_BERZERKER:
			li_speed *= 0.8;
			break;
		case BOTS_INFILTRATOR:
			li_speed *= 1.1;
			break;
		case BOTS_KAMIKAZEE:
			li_speed *= 1.0;
			break;
		case BOTS_NURSE:
			li_speed *= 1.1;
			break;
		case BOTS_SCIENTIST:
			li_speed *= 1.1;
			break;
	}

	return li_speed;
}


// This was just copied from the cg_event.c CG_Obituary function. It was easier than
// trying to mark all of teh places the code had changed

void CG_BOTS_Obituary( entityState_t *ent ) {
	int			mod;
	int			target, attacker;
	char		*message;
	char		*message2;
	char		*locationmsg;
	const char	*targetInfo;
	const char	*attackerInfo;
	char		targetName[32];
	char		attackerName[32];
	gender_t	gender;
	clientInfo_t	*ci;
	int			li_random;	// new
	int			li_locationdmg;

	li_random = rand() & 4;	// new

	target = ent->otherEntityNum;
	attacker = ent->otherEntityNum2;
	mod = ent->eventParm;
	li_locationdmg = ent->powerups;

	ent->powerups = 0;

	if ( target < 0 || target >= MAX_CLIENTS ) {
		CG_Error( "CG_Obituary: target out of range" );
	}
	ci = &cgs.clientinfo[target];

	if ( attacker < 0 || attacker >= MAX_CLIENTS ) {
		attacker = ENTITYNUM_WORLD;
		attackerInfo = NULL;
	} else {
		attackerInfo = CG_ConfigString( CS_PLAYERS + attacker );
	}

	targetInfo = CG_ConfigString( CS_PLAYERS + target );
	if ( !targetInfo ) {
		return;
	}
	Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof(targetName) - 2);
	strcat( targetName, S_COLOR_WHITE );

	message2 = "";

	// check for single client messages

	gender = ci->gender;

	switch( mod ) {
	case MOD_SUICIDE:
		message = "suicides";
		break;
	case MOD_FALLING:
		if ( gender == GENDER_FEMALE )
			message = "broke a nail, and her neck";
		else
		{
			if (li_random == 4)
				message = "validated Newton's Theory";
			else if (li_random == 3)
				message = "has fallen and can't get up!";
			else if (li_random == 2)
				message = "fell and forgot to bounce";
			else
				message = "took a flying leap";
		}
		break;
	case MOD_CRUSH:
		message = "was squished";
		break;
	case MOD_WATER:
		if (li_random == 4)
			message = "sank like a rock";
		else if (li_random == 3)
			message = "gargled and forgot to spit";
		else if (li_random == 2)
			message = "visited Davey Jones'locker";
		else
			message = "was lost at sea";
		break;
	case MOD_SLIME:
		message = "melted";
		break;
	case MOD_LAVA:
		if ( gender == GENDER_FEMALE )
			message = "took a hot bath";
		else
			message = "screamed like a little girl";
		break;
	case MOD_TARGET_LASER:
		message = "saw the light";
		break;
	case MOD_TRIGGER_HURT:
		message = "was in the wrong place";
		break;
	default:
		message = NULL;
		break;
	}

	if (attacker == target) {
		switch (mod) {
		case MOD_NUKE:
			message = "now glows in the dark.";
			break;
		case MOD_CLUSTER:
		case MOD_GRENADE_SPLASH:
			if ( gender == GENDER_FEMALE )
				message = "tripped on her own grenade";
			else if ( gender == GENDER_NEUTER )
				message = "tripped on its own grenade";
			else
				message = "tripped on his own grenade";
			break;
		case MOD_ROCKET_SPLASH:
			if ( gender == GENDER_FEMALE )
				message = "blew herself up";
			else if ( gender == GENDER_NEUTER )
				message = "blew itself up";
			else
				message = "blew himself up";
			break;
		case MOD_PLASMA_SPLASH:
			if ( gender == GENDER_FEMALE )
				message = "melted herself";
			else if ( gender == GENDER_NEUTER )
				message = "melted itself";
			else
				message = "melted himself";
			break;
		case MOD_BFG_SPLASH:
			message = "should have used a smaller gun";
			break;
		default:
			if ( gender == GENDER_FEMALE )
				message = "killed herself";
			else if ( gender == GENDER_NEUTER )
				message = "killed itself";
			else
				message = "killed himself";
			break;
		}
	}

	if (message) {
		CG_Printf( "%s %s.\n", targetName, message);
		return;
	}

	// check for kill messages from the current clientNum
	if ( attacker == cg.snap->ps.clientNum ) {
		char	*s;

		if ( cgs.gametype < GT_TEAM ) {
			s = va("You fragged %s\n%s place with %i", targetName, 
				CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ),
				cg.snap->ps.persistant[PERS_SCORE] );
		} else {
			s = va("You fragged %s", targetName );
		}
		//tbp: don't clog the screen
		//CG_CenterPrint( s, SCREEN_HEIGHT * 0.25, BIGCHAR_WIDTH );

		// print the text message as well
	}

	// check for double client messages
	if ( !attackerInfo ) {
		attacker = ENTITYNUM_WORLD;
		strcpy( attackerName, "noname" );
	} else {
		Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof(attackerName) - 2);
		strcat( attackerName, S_COLOR_WHITE );
		// check for kill messages about the current clientNum
		if ( target == cg.snap->ps.clientNum ) {
			Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) );
		}
	}

	if ( attacker != ENTITYNUM_WORLD ) {
		switch (mod) {
		case MOD_TENDRIL:
			// message = "should have worn insulating soles";
			message = "was electrified by";
			break;
		case MOD_NUKE:
			message = "was introduced to the nuclear family by";
			break;
		case MOD_POISON:
			message = "was poisoned by";
			break;

		case MOD_GRAPPLE:
			message = "was caught by";
			break;
		case MOD_GAUNTLET:
			message = "was pummeled by";
			break;
		case MOD_MACHINEGUN:
			if (li_random == 3)
				message = "recieved a lethal dose of lead from";
			else if (li_random == 2)
				message = "does the machinegun cha-cha with";
			else if (li_random == 3)
				message = "was ventilated by";
			else
				message = "was cut in half by";
			message2 = "'s machinegun";
			break;
		case MOD_SHOTGUN:
			message = "was blown away by";
			message2 = "'s shotgun";
			break;
		case MOD_CLUSTER:
		case MOD_GRENADE:
			message = "ate";
			message2 = "'s grenade";
			break;
		case MOD_GRENADE_SPLASH:
			message = "was shredded by";
			message2 = "'s shrapnel";
			break;
		case MOD_ROCKET:
			if (li_random > 2)
			{
				message = "rides";
				message2 = "'s rocket of Love";
			}
			else if (li_random == 2)
			{
				message = "was on the business end of";
				message2 = "'s rocket";
			}
			else
			{
				message = "ate";
				message2 = "'s rocket";
			}
			break;
		case MOD_ROCKET_SPLASH:
			if (li_random > 2)
			{
				message = "spotted";
				message2 = "'s rocket a little too late";
			}
			else
			{
				message = "almost dodged";
				message2 = "'s rocket";
			}
			break;
		case MOD_PLASMA:
			message = "was melted by";
			message2 = "'s plasmagun";
			break;
		case MOD_PLASMA_SPLASH:
			message = "was melted by";
			message2 = "'s plasmagun";
			break;
		case MOD_RAILGUN:
			message = "fell head over bowels for";
			break;
		case MOD_LIGHTNING:
			message = "was electrocuted by";
			break;
		case MOD_BFG:
		case MOD_BFG_SPLASH:
			message = "was blasted by";
			message2 = "'s BFG";
			break;
		case MOD_TELEFRAG:
			if (li_random > 2)
				message = "became one with";
			else
			{
				message = "tried to invade";
				message2 = "'s personal space";
			}
			break;
		default:
			message = "was killed by";
			break;
		}

		switch ( li_locationdmg & 
				~(LOCATION_BACK | LOCATION_LEFT | LOCATION_RIGHT | LOCATION_FRONT) )
		{
			case LOCATION_HEAD:
				locationmsg = "to the head!";
				break;
			case LOCATION_FACE:
				if (li_locationdmg & LOCATION_FRONT)
					locationmsg = "to the face!";
				else
					locationmsg = "to the head!";
				break;
			case LOCATION_SHOULDER:
				if (li_locationdmg & (LOCATION_FRONT | LOCATION_BACK))
					locationmsg = "to the shoulder!";
				else
					locationmsg = "to the shoulder!";
				break;
			case LOCATION_CHEST:
				if (li_locationdmg & (LOCATION_FRONT | LOCATION_BACK))
					locationmsg = "to the chest!";
				else
					locationmsg = "to the back!";
				break;
			case LOCATION_STOMACH:
				locationmsg = "to the bread basket!";
				break;
			case LOCATION_GROIN:
				if (li_locationdmg & LOCATION_FRONT)
					locationmsg = "to the groin!";
				else
					locationmsg = "to the backside!";
				break;
			case LOCATION_LEG:
				locationmsg = "to the legs!";
				break;
			case LOCATION_FOOT:
				locationmsg = "to the feet!";
				break;
			default:
				locationmsg = "";
				break;
		}

		if (message) {
			CG_Printf( "%s %s %s%s %s\n", 
				targetName, message, attackerName, message2, locationmsg);
			return;
		}
	}

	// we don't know what it was
	CG_Printf( "%s died.\n", targetName );
}

static int		key_pressed_start = 0;
static qboolean qb_key_pressed = qfalse;

void BOTS_plusgren(void)
{
	if (!qb_key_pressed)
	{
		key_pressed_start = cg.time;
		qb_key_pressed = qtrue;
	}
}

void BOTS_minusgren(void)
{
	int		duration = 0;

	if (!qb_key_pressed)
		return;

	qb_key_pressed = qfalse;
	duration = cg.time - key_pressed_start;
	if (duration < 100)
		duration = 0;
	else if (duration < 1000)
		duration = 1000;
	else if (duration > 3000)
		duration = 3000;
	trap_SendClientCommand(va("cgame_to_server_gren %i\n", duration));
}

static qboolean qb_blink = qfalse;
void BOTS_plusblink(void)
{
	qb_blink = qtrue;
	trap_SendClientCommand("plusblink");
}

void BOTS_minusblink(void)
{
	qb_blink = qfalse;
	trap_SendClientCommand("minusblink");
}

void BOTS_blink()
{
	vec4_t color = {0, 0, 0, 1};
	CG_FillRect( 0, 0,  640, 480, color );
}

void BOTS_blind()
{
	vec4_t		color = { 1,1,1,1 }; 

	if (qb_blink) {
		BOTS_blink();
		return;
	}

	//tbp: it's a powerup now
	if (cg.predictedPlayerState.powerups[PW_BLIND] < cg.time)
		return;
	else {
		const int iCOMPLETE_BLINDNESS_TIME = 1000;
		int iTime = cg.predictedPlayerState.powerups[PW_BLIND] - cg.time;
		
		if (iTime<(BOTS_BLIND_TIME - iCOMPLETE_BLINDNESS_TIME))
			color[3] = (float)iTime/(float)(BOTS_BLIND_TIME - iCOMPLETE_BLINDNESS_TIME);
	}

	CG_FillRect( 0, 0,  640, 480, color );
}

void BOTS_decoy_grenades( centity_t *cent )
{
	refEntity_t			ent;
	entityState_t		*s1;
	const weaponInfo_t		*weapon;
	//cgs.media.grenadeExplosionShader

	s1 = &cent->currentState;
	weapon = &cg_weapons[s1->weapon];

	// calculate the axis
	VectorCopy( s1->angles, cent->lerpAngles);

	// create the render entity
	memset (&ent, 0, sizeof(ent));
	VectorCopy( cent->lerpOrigin, ent.origin);
	VectorCopy( cent->lerpOrigin, ent.oldorigin);

	// flicker between two skins
	ent.skinNum = cg.clientFrame & 1;
	ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW;

	// ent.hModel = MODELINDEX_ROCKETLAUNCHER;
	ent.hModel = trap_R_RegisterModel("models/powerups/ammo/plasmaam.md3");

	// convert direction of travel into axis
	if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) {
		ent.axis[0][2] = 1;
	}

	// spin as it moves
	if ( s1->pos.trType != TR_STATIONARY ) {
		RotateAroundDirection( ent.axis, cg.time / 4 );
	} else {
		RotateAroundDirection( ent.axis, s1->time );
	}

	CG_AddRefEntityWithPowerups( &ent, s1->powerups, TEAM_FREE,TEAM_FREE);
}

qhandle_t BOTS_get_decoy_model()
{
	qhandle_t model = trap_R_RegisterModel("models/weapons2/bfg/bfg.md3");
	return model;
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// code by TBP
//
#include "..\game\bots_game2cgame.h"
// Some config string debugging...
void BOTS_Datacount_f(void) {
/*
	CG_Printf("dataCount %i\n",cgs.gameState.dataCount);
	CG_Printf("CS_PLAYERS %i\n",CS_PLAYERS);
	CG_Printf("CS_LOCATIONS %i\n",CS_LOCATIONS);
	CG_Printf("CS_BOTS_CLIENT %i\n",CS_BOTS_CLIENT);
	CG_Printf("CS_BOTS_MENU %i\n",CS_BOTS_MENU);
	CG_Printf("CS_MAX %i\n",CS_MAX);
*/
/*
	CG_Printf("sizeof(bots_game2cgame_t)=%i\n",sizeof(bots_game2cgame_t));
	CG_Printf("sizeof(bots_g2cg_generic_t)=%i\n",sizeof(bots_g2cg_generic_t));
	CG_Printf("sizeof(bots_g2cg_sniper_t)=%i\n",sizeof(bots_g2cg_sniper_t));
*/
	CG_Printf("cg_deferPlayers %i (mod# %i)\n",cg_deferPlayers.integer,cg_deferPlayers.modificationCount);
	trap_Cvar_Set( "cg_deferPlayers", "0" );

	CG_Printf("sizeof(bots_g2cg_weapons_t)=%i\n",sizeof(bots_g2cg_weapons_t));
	if (qtrue) {
		bots_game2cgame_t	*p = (void *)&cg.predictedPlayerState.stats[STAT_GAME2CGAME];
		CG_Printf("p:%i t:%i pw:%i\n",p->ucPromoPoints,p->ucTechPoints,p->ucPowerup);

		// Captain
		if (qtrue) {
			bots_g2cg_captain_t	*s = &p->onion.captain;
			CG_Printf("captain: key:%i warcry:%i  g:%i gm:%i wm:%i\n",
				s->sKeyDistance,s->sWarcryDelay,
				s->weapons.gren1,s->weapons.grenademode,s->weapons.weaponmode);
		}
		// BG
		if (qtrue) {
			bots_g2cg_bodyguard_t	*s = &p->onion.bodyguard;
			CG_Printf("bg: lt:%i decoy:%i  protected:%i proxies:%i grenmode:%i pad:%i\n",
				s->sLaserTime ,s->sDecoyDelay, s->sProtected,
				s->weapons.gren1,s->weapons.grenademode,s->weapons.padding);
		}
		// Sniper
		if (qtrue) {
			bots_g2cg_sniper_t	*s = &p->onion.sniper;
			CG_Printf("sniper: bits:%i cloaktime:%i\n",s->bits,s->sCloakTime);
		}
	}
}

typedef struct {
	char *fileName;
	char *description;
} radioMsg_t;

 
radioMsg_t radioMsgs[iMAX_RADIO_MSG] = {
	{ "atackflag",	"Attacking the Flag!" },
	{ "captain",	"Captain going to Promote" }, 
	{ "damnit",		"Damn!" },
	{ "disinifil",	"Disguised Infiltrator!" }, 
	{ "enemyflag",	"Enemy has Flag!" },
	{ "flagbackup",	"Flag Backup!" },
	{ "incoming",	"Incoming!" },
	{ "keyattack",	"Key is under Attack!"},
	{ "medic",		"Medic!" },
	{ "promo",		"Requesting Promo" },
	{ "roger",		"Roger!" }
};


void BOTS_RadioRegisterSounds( void ) {
	int i;

	CG_Printf("...Registering radio sounds\n");
	for (i=0; i<iMAX_RADIO_MSG; i++) {
		char sFullPath[256];

		// Female
		Com_sprintf(sFullPath, 256, "sound/radio/%s1.wav",radioMsgs[i].fileName);
		cgs.media.radioSounds[i][0] = trap_S_RegisterSound(sFullPath,qfalse);
		if (cgs.media.radioSounds[i][0] == 0)
			CG_Printf(va("Failed loading of radio snd: '%s'\n",sFullPath));

		// Male
		Com_sprintf(sFullPath, 256, "sound/radio/%s2.wav",radioMsgs[i].fileName);
		cgs.media.radioSounds[i][1] = trap_S_RegisterSound(sFullPath,qfalse);
		if (cgs.media.radioSounds[i][1] == 0)
			CG_Printf(va("Failed loading of radio snd: '%s'\n",sFullPath));
	}
}

void BOTS_Radiolist_f(void) {
	int i;

	CG_Printf("Radio message list:\n");
	for (i=0; i<iMAX_RADIO_MSG; i++)
		CG_Printf(va("   %i: %s\n",i,radioMsgs[i].description));
}

void BOTS_Radio_display(int sourceNum, int radiomsgNum) {
	char		sourceName[32];
	const char	*sourceInfo;

	if ( sourceNum < 0 || sourceNum >= MAX_CLIENTS ) {
		CG_Printf( "BOTS_Radio_display: sourceNum out of range" );
		return;
	}
	if ((radiomsgNum<0) || (radiomsgNum>=iMAX_RADIO_MSG))
		return;

	sourceInfo = CG_ConfigString( CS_PLAYERS + sourceNum );
	if ( !sourceInfo ) {
		return;
	}
	Q_strncpyz( sourceName, Info_ValueForKey( sourceInfo, "n" ), sizeof(sourceName) - 2);
	strcat( sourceName, S_COLOR_WHITE );

	CG_Printf(va("^2[radio]^7 %s^7: %s\n",
		sourceName,
		radioMsgs[radiomsgNum].description ));
}

void BOTS_Radiotog_f(void) {
	cg.bRadioIsMuted = !cg.bRadioIsMuted;
	
	if (cg.bRadioIsMuted) {
		CG_Printf("^2Your radio has been disabled.\n^7");
	}
	else
		CG_Printf("^2Your radio has been enabled.\n^7");
}

void BOTS_Radio_f(void) {
	char	sNumber[256];
	int		num;
	const	int	iRADIO_DEBOUNCE_TIME = 3500;

	trap_Argv( 1, sNumber, 256 );
	// Some integrity checking
	num = atoi(sNumber);
	if ((strlen(sNumber) > 0) && (num >= 0) && (num < iMAX_RADIO_MSG)) {	
		if (cg.time > cg.iRadioDebounce) {
			trap_SendClientCommand(va("radio_broadcast %s\n", sNumber));
			cg.iRadioDebounce = cg.time + iRADIO_DEBOUNCE_TIME;
		}
		else
			CG_Printf("^2Your radio hasn't cooled down enough...^7\n");
	}
	else
		CG_Printf("usage: radio # (ie: /radio 1)\n");
}


#define alMAXLOCKS             256 // K_LAST_KEY, no info available => wild guess
static qboolean alLocks[alMAXLOCKS];

void CG_AliasUp_f (void) 
{
	char	script[128];
	int     key;

	trap_Argv( 2, script, 128 );
	key = atoi(script) & (alMAXLOCKS -1);
	alLocks[key] = qfalse;          

	trap_Argv( 1, script, 100 );
	Q_strcat(script,128,"up");
	trap_SendConsoleCommand( va( "vstr %s", script ) );
}

void CG_AliasDown_f (void) 
{
	char    script[128];
	int     key;

	trap_Argv( 2, script, 128 );
	key = atoi(script) & (alMAXLOCKS -1);

	if (alLocks[key])
		return;
	else
		alLocks[key] = qtrue;

	trap_Argv( 1, script, 100 );
	Q_strcat(script,128,"down");
	trap_SendConsoleCommand( va( "vstr %s", script ) );
}

#define M_HPI		(M_PI/2.0f)

/* Add *some* localentities (fragments) */
void FX_GibFountain( vec3_t origin ) {
	float			
		angle,
		fVelocityV		= 180.0f, // Initial vertical velocity
		fVelocityH		= 800.0f; // Initial horizontal velocity
		
	const int
		iGIBS_PER_LAYER	= 16,	// 
		iMAX_LAYERS		= 7,	// Nb of successive layers
		iLIFETIME		= 4000;	// Duration
	const float		
		step			= (2.0*M_PI)/(float)iGIBS_PER_LAYER,
		fANGULAR_JITTER	= (M_PI/16.0f),	// Each gib is launched with a bit angular velocity randomness
		fOFFSET_Z		= 0.0f /*12.0f*/,		// Initial Z offset
		fATTENUATION_H = 1.1f,
		fATTENUATION_V = 1.2f;

	
	localEntity_t	*le;
	refEntity_t		*re;

	int	i,
		pass			= iMAX_LAYERS; 
	// float waterScale = 1.0f;
	


	// Move it up a bit
	origin[2]		+= fOFFSET_Z;

	// Pseudo particules generation.
	while (pass--) {
		i		= iGIBS_PER_LAYER;
		angle	= crandom() * M_PI; // Starts that layer somewhere
		while (i--) {
			float jitter = crandom() * fANGULAR_JITTER;

			le = CG_AllocLocalEntity();
			re = &le->refEntity;
			
			le->leType				= LE_FRAGMENT;
			le->leFlags				= LEF_TUMBLE;
			le->leBounceSoundType	= LEBS_NONE;
			le->leMarkType			= LEMT_NONE;

			le->startTime	= cg.time;
			le->endTime		= le->startTime + iLIFETIME; 
			// cg_brassTime.integer + ( cg_brassTime.integer / 4 ) * random();
			
			le->pos.trType	= TR_GRAVITY; // TR_LINEAR; 
			le->pos.trTime	= cg.time - ( (rand()&127) + 20); // Add some jitter to its spawning
			
			le->pos.trDelta[0] = sin(angle+jitter) * fVelocityH;
			le->pos.trDelta[1] = cos(angle+jitter) * fVelocityH;
			le->pos.trDelta[2] = fVelocityV;
			angle += step;
			
			VectorCopy( origin, re->origin );
			VectorCopy( origin, le->pos.trBase );



			// Chose a model
			// More brains (statistically) than other parts
			switch(rand()&7) {
			case 0:
				re->hModel = cgs.media.gibArm;				
				break;
			case 1:
				re->hModel = cgs.media.gibSkull;
				break;
			case 2:
				re->hModel = cgs.media.gibIntestine;
				break;
			case 3:
				re->hModel = cgs.media.gibFist;
				break;
			default:
				re->hModel = cgs.media.gibBrain;
				break;
			}
			// re->hModel			= cgs.media.gibBrain; // cgs.media.lightningExplosionModel;
			// re->customShader	= cgs.media.lightningShader;


			/* Do we care about underwater nukes? It's a nuke afterall
			if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) {
			waterScale = 0.10;
			}
			*/
			le->bounceFactor		= 0.5; // 0.4; // * waterScale;			
	
			// Give it some randomized angle/rotation
			le->angles.trType		= TR_GRAVITY; // TR_LINEAR; // TR_STATIONARY;		
			le->angles.trTime		= cg.time - (rand()&15);
			le->angles.trBase[0]	= rand()&255;
			le->angles.trBase[1]	= rand()&255;
			le->angles.trBase[2]	= rand()&255;
			le->angles.trDelta[0]	= rand()&31;
			le->angles.trDelta[1]	= rand()&15;
			le->angles.trDelta[2]	= 0; // rand()&31;
		}
		// While going from the external layer to the inner one, gibs tend to go higher but less far.
		fVelocityV		*= fATTENUATION_V;
		fVelocityH		/= fATTENUATION_H;
	}

}


/* Add a bunch of poly to the scene
 How do you spell 'brain-dead'?
 What about the use of a lookup table + translation/scaling?
 Motto: burn some cpu cycles, dammit!
 Note:	seems like polys have a special treatment when it comes to shaders. 
		1 stage, no vertex modulation(they are used, but are not propagated). i'm puzzled.
*/
void FX_TesselateSphere(vec3_t origin, qhandle_t shader, float uSeed, float dtheta, float dphi, float radius)
{
	float		theta,phi, u0, v0, du, dv;
	polyVert_t	p[4];
	int			n;

	
	uSeed	/= 360.0f; // Normalized to (0,1)

	// Vertex lighting and alpha
	// My ass! Can't get vertex modulation to work properly.
	// Deep mystery.
	for (n=0; n<4; n++) {
		
		p[n].modulate[0] = 255;
		p[n].modulate[1] = 255;
		p[n].modulate[2] = 255;
		p[n].modulate[3] = (int)radius & 255;
		
	}

	du = dtheta / ((2.0f*M_PI));
	dv = dphi / ((2.0f*M_HPI));
	v0 = 1 - dv;
	for (theta = -M_HPI; theta <= (M_HPI); theta += dtheta) {
		u0 = uSeed; // A bit of random initial shifting on the u coord.
		for (phi = 0; phi <= ((2.0f*M_PI)); phi += dphi) {
			float	u1,v1;

			// Texture coordinate generation.
			// Maps (0,1) from "left" to "right" => u
			// Maps (0,1) from "bottom" to "top" => v
			u1 = u0 + du;
			v1 = v0 + dv;
			
			p[0].st[0] = u0;
			p[0].st[1] = v1;
			
			p[1].st[0] = u0;
			p[1].st[1] = v0;
			
			p[2].st[0] = u1;
			p[2].st[1] = v0;
			
			p[3].st[0] = u1;
			p[3].st[1] = v1;
			
			u0 = u1;

			// Some cos/sin could be pre calculated, but a table would be waaaaayyy better.
			p[0].xyz[0] = (cos(theta) * cos(phi)) * radius + origin[0];
			p[0].xyz[1] = (cos(theta) * sin(phi)) * radius + origin[1];
			p[0].xyz[2] = (sin(theta)) * radius + origin[2];

			p[1].xyz[0] = (cos((theta+dtheta)) * cos(phi)) * radius + origin[0];
			p[1].xyz[1] = (cos((theta+dtheta)) * sin(phi)) * radius + origin[1];
			p[1].xyz[2] = (sin((theta+dtheta))) * radius + origin[2];

			p[2].xyz[0] = (cos((theta+dtheta)) * cos((phi+dphi))) * radius + origin[0];
			p[2].xyz[1] = (cos((theta+dtheta)) * sin((phi+dphi))) * radius + origin[1];
			p[2].xyz[2] = (sin((theta+dtheta))) * radius + origin[2];

			if (theta > -M_HPI && theta < M_HPI) {
				p[3].xyz[0] = (cos(theta) * cos((phi+dphi))) * radius + origin[0];
				p[3].xyz[1] = (cos(theta) * sin((phi+dphi))) * radius + origin[1];
				p[3].xyz[2] = (sin(theta)) * radius + origin[2];
				// Render the poly			
				trap_R_AddPolyToScene(
					shader,
					4, 
					p);
			}
			else		
				// Render the poly			
				trap_R_AddPolyToScene(
					shader,
					3, 
					p);
		}
		v0 -= dv;
	}
	// CG_Printf(va("polys %d\n", count));
}

// Quick&dirty smoke gen.
// Spawn some smoke puffs bigger when near the epicenter.
// NB: Those are quite expensive to render (to say the least ;)
void FX_SmokeCloudGenerator(vec3_t origin, int msec)
{
	const float	
		fDISTANCE_BIAS		= 0.0f,
		fHEIGHT_BIAS		= 20.0f,
		fINITIAL_RADIUS		= 20.0f,
		fINITIAL_DISTANCE	= 180.0f,
		fJITTER_SCALE		= (M_PI/30.0f),
		fRADIUS_RATE		= 2.0f,
		fDISTANCE_RATE		= 0.8f;

	const int 
		iNB_PER_LAYER	= 8,
		iMAX_LAYERS		= 10;
	
	localEntity_t	*smoke;
	vec3_t			p,up;
	float			radius, distance,a,da;
	int				layer,i;
	
	
	VectorSet( up, 0, 0, 8 );
	radius		= fINITIAL_RADIUS;
	distance	= fINITIAL_DISTANCE;
	da			= (2.0f*M_PI)/(float) iNB_PER_LAYER;
	for (layer=0; layer<iMAX_LAYERS; layer++) {
		a = crandom() * M_PI;
		for (i=0; i<iNB_PER_LAYER; i++) {
			float  red;
			float jitter = (crandom() * fJITTER_SCALE);
			VectorSet( p,
				(sin(a+jitter)*distance) + fDISTANCE_BIAS + origin[0], 
				(cos(a+jitter)*distance) + fDISTANCE_BIAS + origin[1], 
				fHEIGHT_BIAS + origin[2]);

			red = (rand()&20);
			if (red == 0)
				red = 2;
			red = 1/red;
			
			//if (i % 2 == 0)
			if (1)
			{
				//CG_Printf("rand = %f\n", (float)red);
				smoke = CG_SmokePuff(p, up,
									 radius+(rand()&63),	// Radius + randomization
									 1, 1, 1, 0.9,		// RGBA
									 msec + 500 - (rand()&255),	// Duration (a bit biased)
									 cg.time,
									 0,						LE_SCALE_FADE, /*leFlags*/ 
									 cgs.media.smokePuffShader);
			}
			else
			{
				smoke = CG_SmokePuff(p, up,
									 radius+(rand()&63),	// Radius + randomization
									 1, .25, 0, 0.9,		// RGBA
									 msec + 500 - (rand()&255),	// Duration (a bit biased)
									 cg.time,
									 0,						LE_SCALE_FADE, /*leFlags*/ 
									 cgs.media.smokePuffShader);
			}
			// use the optimized local entity add
			smoke->leType	= LE_SCALE_FADE;
			
			a += da;
		}
		radius		*= fRADIUS_RATE;
		distance	*= fDISTANCE_RATE;
	}
}

// Spawn:
// . a magnificent fungi. (yet to be modeled. hmm)
// . a huge blast
// . some smoke
localEntity_t *CG_NukeFX( vec3_t origin, qhandle_t hModel, qhandle_t shader, int msec) {
	localEntity_t	*ex;

	if ( msec <= 0 ) {
		CG_Error( "CG_NukeFX: msec = %i", msec );
	}


	// ------------ Allocate a BallOfFire ----------------------
	//
	ex = CG_AllocLocalEntity();
	ex->leType				= LE_NUKE_BALLOFFIRE;
	ex->pos.trType			= TR_STATIONARY;
	ex->radius				= 1200.0f;		// It's supposed to be huge, isn't it? ;)
	ex->refEntity.rotation	= rand() % 360; // Horizontal texture rotation (u shifting)

	ex->startTime	= cg.time; //  - offset;
	ex->endTime		= ex->startTime + msec;
	// bias the time so all shader effects start correctly
	ex->refEntity.reType		= RT_POLY;
	ex->refEntity.shaderTime	= ex->startTime / 1000.0f; // Crap, can't get it to work properly.

	// ex->refEntity.hModel = hModel;
	ex->refEntity.customShader = cgs.media.nukeBallOfFireShader;
	VectorCopy( origin, ex->refEntity.origin );
	ex->color[0] = ex->color[1] = ex->color[2] = 1.0;


	// ------------ Allocate a MushRoom   ----------------------
	//
	ex = CG_AllocLocalEntity();
	ex->leType = LE_NUKE_MUSHROOM;
	ex->pos.trType	= TR_STATIONARY;
	ex->startTime	= cg.time;  // - offset;
	ex->endTime		= ex->startTime + msec;
	//ex->radius		= 2; // Used to generate smoke

	// bias the time so all shader effects start correctly
	ex->refEntity.shaderTime	= ex->startTime / 1000.0f;
	ex->refEntity.reType		= RT_MODEL;
	ex->refEntity.hModel		= cgs.media.machinegunBrassModel;//hModel;
	ex->refEntity.customShader	= shader;

	AxisCopy( axisDefault, ex->refEntity.axis );
	VectorCopy( origin, ex->refEntity.origin );
	VectorCopy( origin, ex->refEntity.oldorigin );

	ex->color[0] = ex->color[1] = ex->color[2] = 1.0;

	// ------------ Smoke generator      ----------------------
	//
	FX_SmokeCloudGenerator(origin, msec);
	return ex; // Bah.
}

// Healradius effect
void BOTS_HealRadius(int clientNum, int nurseNum) {
	clientInfo_t
		*ciNurse	= &cgs.clientinfo[nurseNum];
	localEntity_t	*ex;

	

	// If _the_ player is being healed, tell him
	if (clientNum == cg.snap->ps.clientNum) {
		if ( clientNum == nurseNum ) 
			CG_Printf("^3You healed yourself.^3\n");
		else {
			if (ciNurse->infoValid)
				CG_Printf(va("^3You have been healed by ^7%s^3.^7\n",ciNurse->name));
			else
				CG_Printf("BOTS_HealRadius: OOPS, invalid nurse clientInfo\n");
		}
	}

	// Allocate a local entity that won't be rendered directly
	ex = CG_AllocLocalEntity();
	ex->leType				= LE_HEALRADIUS;
	ex->leFlags				= clientNum;	// Store it somewhere, needed to track this entity position
	ex->pos.trType			= TR_STATIONARY;
	ex->refEntity.rotation	= random() * 2.0f * M_PI; // Initial random rotation

	ex->startTime			= cg.time;
	ex->endTime				= ex->startTime + 4000;

	ex->refEntity.reType		= RT_SPRITE;
	ex->refEntity.shaderTime	= ex->startTime / 1000.0f; 

	ex->refEntity.hModel		= cgs.media.dishFlashModel;
	ex->refEntity.customShader	= cgs.media.waterBubbleShader;

	ex->refEntity.shaderRGBA[0] = 0xff;
	ex->refEntity.shaderRGBA[1] = 0xff;
	ex->refEntity.shaderRGBA[2] = 0xff;
	ex->refEntity.shaderRGBA[3] = 0xff;

	ex->color[0] = ex->color[1] = ex->color[2] = 1.0;
}

// Tendril effect
void BOTS_Tendril(centity_t *cent, int clientNum) {
	localEntity_t	*le;
	refEntity_t		*re;
	int				botsTendrilTime = 400; // 200


	//
	// core
	//
	le = CG_AllocLocalEntity();
	re = &le->refEntity;

	// Store it somewhere.
	le->leMarkType			= cent - cg_entities;
	le->leBounceSoundType	= clientNum;



	le->leType = LE_TENDRIL;
	le->startTime = cg.time;
	le->endTime = cg.time + botsTendrilTime;//cg_railTrailTime.value;
	le->lifeRate = 1.0 / botsTendrilTime;

	re->shaderTime = cg.time / 1000.0f;
	re->reType = RT_RAIL_CORE;

	re->customShader = 
		// cgs.media.railRingsShader;
		cgs.media.railCoreShader;

	//VectorCopy( start, re->origin );
	//VectorCopy( end, re->oldorigin );

	// (0.5, 1.0, 0.5, 1.0), from Wedge
	// Let's make it a tad less greenish
	le->color[0] = 0.6;
	le->color[1] = 0.7;
	le->color[2] = 0.6;
	le->color[3] = 1.0;

	AxisClear( re->axis );
}

void BOTS_Warcry(int clientNum) {
	localEntity_t	*le;
	refEntity_t		*re;


	le = CG_AllocLocalEntity();
	re = &le->refEntity;

	// Store it somewhere.
	le->leMarkType			= clientNum;

	le->leType		= LE_WARCRY;
	le->startTime	= cg.time;
	le->endTime		= cg.time + 1500;
	le->lifeRate	= 10.0f; // Initial size/radius (from model)

	re->shaderTime = (cg.time + (rand()&255)) / 1000.0f; // Randomize it
	re->reType = RT_MODEL;

	re->hModel = cgs.media.warcryModel;
	// re->customShader = cgs.media.railRingsShader;

	le->color[0] = 1.0f;
	le->color[1] = 1.0f;
	le->color[2] = 1.0f;
	le->color[3] = 1.0f;

	AxisClear( re->axis );
}



void BOTS_Effect1(vec3_t origin) {
	localEntity_t	*le;
	refEntity_t		*re;
	const int
		iEffectTime = 350;


	CG_Printf("BOTS_Effect1 %i\n",cg.time);
	//
	// core
	//
	le = CG_AllocLocalEntity();
	re = &le->refEntity;

	// Store it somewhere.
	//le->leMarkType			= cent - cg_entities;
	// le->leBounceSoundType	= clientNum;
	le->leBounceSoundType = 0;



	le->leType		= LE_EFFECT1;
	le->startTime	= cg.time;
	le->endTime		= cg.time + iEffectTime;
	le->lifeRate	= 1.0 / iEffectTime;
	le->radius		= 10;
	
	re->rotation = 0;
	re->shaderTime = cg.time / 1000.0f;
	re->reType = RT_SPRITE;
	re->radius = le->radius;

	re->customShader = 
		cgs.media.railRingsShader;
		// cgs.media.railCoreShader;

	VectorCopy( origin, re->origin );
	VectorCopy( origin, re->oldorigin );

	le->pos.trType = TR_STATIONARY;
	le->pos.trTime = cg.time;
	VectorCopy( origin, le->pos.trBase );

	le->color[0] = 	le->color[1] = 	le->color[2] = 	le->color[3] = 1.0;
	re->shaderRGBA[0] = re->shaderRGBA[1] =	re->shaderRGBA[2] = re->shaderRGBA[3] = 0xff;

	AxisClear( re->axis );
}

void FX_Particle_SnowBox(vec3_t origin, int iNb, const int iLifeTime, const float fRadius, const int iGravity ) {	
	localEntity_t	*le;
	refEntity_t		*re;
	vec3_t			pos;


	// CG_Printf("FX_Particle_SnowBox: nb %i radius %i lifetime %i gravity %i\n",iNb,(int)fRadius,iLifeTime,iGravity);
	while (iNb--) {
		le = CG_AllocLocalEntity();
		re = &le->refEntity;
		
		le->leType		= LE_PARTICLE;
		le->leFlags		= LEF_NO_COLLISION|LEF_FADE_RGB|LEF_SCINTILLATION; // LEF_FADE_ALPHA // LEF_NO_STATIONARY|LEF_RANDOM_BOUNCE_SOUND|LEF_FADE_RGB;
		
		le->startTime	= cg.time;
		le->endTime		= le->startTime + iLifeTime + (crandom()*iLifeTime*0.5f); 
		le->lifeRate 	= 1.0f/(le->endTime - le->startTime); 
		
		le->pos.trType		= TR_CUSTOM_GRAVITY; // TR_LINEAR; 
		le->pos.trTime		= cg.time - ( (rand()&127) + 20); // Add some jitter to its spawning
		le->pos.trDuration	= iGravity; // DEFAULT_GRAVITY = 800
		le->radius			= 0.3f+(random()*2.0f);

		
		pos[0] = origin[0] + (crandom()*fRadius);
		pos[1] = origin[1] + (crandom()*fRadius);
		pos[2] = origin[2] + (random()*fRadius);
		VectorCopy( pos, le->pos.trBase );
		VectorCopy( pos, re->origin );
		
		re->customShader	=	
								//cgs.media.golddust_blue_Shader;
								//rand()&3?
								//trap_R_RegisterShader( "bots/particle01" ):
								trap_R_RegisterShader( "bots/particle00" );

		re->reType		= RT_SPRITE;
		re->rotation	= rand()%360;

		le->pos.trDelta[0]	= le->pos.trDelta[1] = le->pos.trDelta[2] = 0;

		le->color[0] = le->color[1] = le->color[2] = 0.6f; // 0.33f;
		le->color[3] = 0.7f;
	}
}

// Flashbang
void BOTS_Effect2(vec3_t origin) {
	localEntity_t	*le;
	refEntity_t		*re;
	const int
		iEffectTime = 400;


	// CG_Printf("BOTS_Effect2 %i\n",cg.time);

	le = CG_AllocLocalEntity();
	re = &le->refEntity;

	re->shaderTime = cg.time / 1000.0f;
	re->hModel		 = 
		trap_R_RegisterModel( "models/bots/fx-hemi.md3" );
		//trap_R_RegisterModel( "models/weaphits/bullet.md3" );
		// cgs.media.ringFlashModel;
		// cgs.media.warcryModel;
	re->customShader = 
		//cgs.media.plasmaExplosionShader;
		// cgs.media.warcryShader;
		//trap_R_RegisterShader( "bots/flashbangDisc" );
		trap_R_RegisterShader( "bots/flashbangBlast" );

	VectorCopy( origin, re->origin );
	VectorCopy( origin, re->oldorigin );

	le->leType		= LE_FLASHBANG;
	le->startTime	= cg.time;
	le->endTime		= cg.time + iEffectTime;
	le->lifeRate	= 1.0 / iEffectTime;
	le->pos.trType	= TR_STATIONARY;
	le->pos.trTime	= cg.time;

	le->radius		= 150;

	le->color[0] = 	le->color[1] = 	le->color[2] = 	le->color[3] = 1.0;

	FX_Particle_SnowBox(re->origin,cg_extravaganza.integer>50?cg_extravaganza.integer:50,1500,150,40);

	trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.sfx_rockexp );
}

// Ray test
void BOTS_Effect3(vec3_t origin) {
	localEntity_t	*le;
	refEntity_t		*re;
	const int
		iEffectTime = 10000;


	//
	// core
	//
	le = CG_AllocLocalEntity();
	re = &le->refEntity;


	le->leType = LE_RAY;
	le->startTime = cg.time;
	le->endTime = cg.time + iEffectTime;
	le->lifeRate = 1.0 / iEffectTime;

	le->radius =  (rand()&63) + 1;

	switch (cg_extravaganza.integer) {
	case 0:
		CG_Printf("RT_LIGHTNING %i\n",(int)le->radius);
		le->leMarkType  = RT_LIGHTNING;
		break;
	case 1:
		CG_Printf("RT_RAIL_CORE %i\n",(int)le->radius);
		le->leMarkType = RT_RAIL_CORE;
		break;
	case 2:
		CG_Printf("RT_RAIL_RINGS %i\n",(int)le->radius);
		le->leMarkType = RT_RAIL_RINGS;
		break;
	default:
		CG_Printf("RT_BEAM %i\n",(int)le->radius);
		le->leMarkType = RT_BEAM;
		break;
	}

	re->shaderTime = cg.time / 1000.0f;
	re->reType = 
		RT_BEAM;
		// RT_RAIL_CORE;

	/*
	re->customShader = 
		// cgs.media.railRingsShader;
		// cgs.media.railCoreShader;
		trap_R_RegisterShader("bots/Ray");
	*/

	VectorCopy( cg.refdef.vieworg,	le->pos.trBase );
	VectorCopy( origin,				le->pos.trDelta );

	le->color[0] = 1; le->color[1] = 1; le->color[2] = 1; le->color[3] = 1;

	AxisClear( re->axis );
}

void BOTS_Effect4(vec3_t origin) {
	CG_Printf("origin(%i,%i,%i)\n", (int)origin[0], (int)origin[1], (int)origin[2]);
}
void BOTS_Effect5(vec3_t origin) {
	CG_Printf("origin(%i,%i,%i)\n", (int)origin[0], (int)origin[1], (int)origin[2]);
}
